package com.xqboss.apps.service.nft;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.xqboss.apps.constant.LockKey;
import com.xqboss.apps.domain.marketing.MarketingAirdrop;
import com.xqboss.apps.domain.marketing.MarketingAirdropRecord;
import com.xqboss.apps.domain.member.SysUserNft;
import com.xqboss.apps.domain.nft.Nft;
import com.xqboss.apps.domain.nft.NftDispatchRecord;
import com.xqboss.apps.domain.nft.NftNumber;
import com.xqboss.apps.domain.order.OrderItem;
import com.xqboss.apps.domain.user.UserBox;
import com.xqboss.apps.domain.user.UserBoxNft;
import com.xqboss.apps.dto.nft.NftNumberDto;
import com.xqboss.apps.enums.nft.NftDispatchSourceEnum;
import com.xqboss.apps.enums.nft.NftNumberGrantStatusEnum;
import com.xqboss.apps.enums.nft.NftTypeEnum;
import com.xqboss.apps.enums.nft.UserNftStatusEnum;
import com.xqboss.apps.enums.user.UserNftSourceTypeEnum;
import com.xqboss.apps.mapper.nft.NftNumberMapper;
import com.xqboss.apps.service.equipment.EquipmentService;
import com.xqboss.apps.service.order.NftDispatchRecordService;
import com.xqboss.apps.service.punch.PunchUserConfigService;
import com.xqboss.apps.service.user.SysUserNftService;
import com.xqboss.apps.util.mh.CommonUtil;
import com.xqboss.apps.vo.nft.WebNftNumberVo;
import com.xqboss.apps.vo.user.UserNFTItemVo;
import com.xqboss.chain.enums.UpChainTypeEnum;
import com.xqboss.chain.service.UpUpdateChainService;
import com.xqboss.common.core.redis.lock.LockObject;
import com.xqboss.common.core.redis.lock.LockValue;
import com.xqboss.common.core.redis.lock.RedisLock;
import com.xqboss.common.core.text.StrFormatter;
import com.xqboss.common.exception.ServiceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * nft随机前缀/编号 服务实现类
 * </p>
 *
 * @author HaoLun
 * @since 2022-10-10
 */
@Service
public class NftNumberService extends ServiceImpl<NftNumberMapper, NftNumber> implements IService<NftNumber> {

    @Resource
    private NftNumberMapper nftNumberMapper;
    @Resource
    private NftPublishLogService nftPublishLogService;

    @Autowired
    private EquipmentService equipmentService;

    @Autowired
    private SysUserNftService userNftService;

    @Autowired
    private NftService nftService;

    @Autowired
    private UpUpdateChainService upUpdateChainService;

    @Autowired
    private PunchUserConfigService punchUserConfigService;

    @Autowired
    private NftDispatchRecordService nftDispatchRecordService;

    /**
     * 保存藏品编号
     */
    public void createNftNumber(Long nftId, List<String> arr, String prefix) {
        if (arr == null) {
            throw new ServiceException("规则类型为空");
        }
        long count = count(Wrappers.<NftNumber>lambdaQuery().eq(NftNumber::getNftId, nftId).last("limit 1")); // 获取总条数，用于排序字段

        List<NftNumber> nftNumberList = new ArrayList<>();
//        List<NftPublishLog> nftPublishLogList = new ArrayList<>();
        NftNumber nftBatch = getOne(Wrappers.<NftNumber>lambdaQuery().eq(NftNumber::getNftId, nftId).orderByDesc(NftNumber::getBatch).last("limit 1"));
        Long batch = 0L;
        if (nftBatch != null) {
            batch = nftBatch.getBatch() + 1; // 批次
        }
        int size = arr.size();
        for (int i = 0; i < size; i++) {
            NftNumber nftNumber = initNftNumber(nftId, batch, Long.valueOf(arr.get(i)), prefix, count++);
            nftNumberList.add(nftNumber);
            //初始化发布历史
//            NftNumberBO nftNumberBO = new NftNumberBO();
//            if (nftNumberBOS != null && !nftNumberBOS.isEmpty()) {
//                nftNumberBO = nftNumberBOS.get(i);
//            }
//            NftPublishLog nftPublishLog = nftPublishLogService.init(nftNumber.getNftId(), nftNumber.getNumberValueStr(), nftNumberBO);
//            nftPublishLogList.add(nftPublishLog);
            if (i % 1000 == 0 || i == size - 1) {
                // 插入数据
                nftNumberMapper.insertBatch(nftNumberList);
                nftNumberList = new ArrayList<>();
                //插入发行数据
//                nftPublishLogService.saveBatch(nftPublishLogList);
//                nftPublishLogList = new ArrayList<>();
            }
        }
    }


    /**
     * 生成对象
     *
     * @param nftId       藏品id
     * @param batch       批次
     * @param numberValue 编号数值
     * @param sort        排序
     * @return
     */
    public NftNumber initNftNumber(Long nftId, Long batch, Long numberValue, String prefix, Long sort) {
        NftNumber nftNumber = new NftNumber();
        Date date = new Date();
        nftNumber.setNftId(nftId);
        nftNumber.setBatch(batch);
        nftNumber.setNumberValue(numberValue);
        nftNumber.setNumberValueStr(prefix + "/" + CommonUtil.getSelfAccretionStr(numberValue.intValue()));
        nftNumber.setDataEnable(true);
        nftNumber.setNftSort(sort);
        nftNumber.setIsGrant(NftNumberGrantStatusEnum.UNSOLD);
        nftNumber.setCreateTime(date);
//        nftNumber.setUpdateTime(date);
        return nftNumber;
    }

    public List<WebNftNumberVo> numberList(NftNumberDto dto) {
        PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
        return nftNumberMapper.numberList(dto);
    }

    /**
     * 提取暂未售出的藏品编号
     *
     * @param nftId  藏品ID
     * @param count  提取数量
     * @param random 是否随机取
     * @return
     */
    private List<NftNumber> extractUnsoldNftNumber(Long nftId, Integer count, boolean random) {
        List<NftNumber> list = list(new LambdaQueryWrapper<NftNumber>()
                .eq(NftNumber::getNftId, nftId)
                .eq(NftNumber::getIsGrant, NftNumberGrantStatusEnum.UNSOLD)
                .orderByAsc(random ? NftNumber::getNftSort : NftNumber::getNumberValue)
                .last("LIMIT " + count)
        );
        if (list.size() < count) {
            throw new ServiceException("典藏库存不足！");
        }
        return list.subList(0, count);
    }


    /**
     * 发放订单藏品
     * @param nftId
     * @param orderItem
     * @param randomNumber
     * @return
     */
    @RedisLock(lockKey = LockKey.LOCK_NFT_NUMBER, timeout = 60, waitTime = 30)
    public List<SysUserNft> distributeNftNumber(@LockValue Long nftId, OrderItem orderItem, Integer quantity, boolean randomNumber){
        return _distributeNftNumber(nftId, orderItem.getUserId(), quantity, orderItem.getOrderMainId(), orderItem.getId(),
                NftDispatchSourceEnum.ORDER, UserNftSourceTypeEnum.PUBLISH, randomNumber, LocalDateTime.now());
    }

    @RedisLock(lockKey = LockKey.LOCK_NFT_NUMBER, timeout = 60, waitTime = 30)
    public List<SysUserNft> distributeNftNumber(Long userId, @LockValue Long nftId, Long orderId, Long orderItemId, Integer quantity, boolean randomNumber, LocalDateTime saleableTime, NftDispatchSourceEnum dispatchSource, UserNftSourceTypeEnum nftSource){
        return _distributeNftNumber(nftId, userId, quantity, orderId, orderItemId,
                dispatchSource, nftSource, randomNumber, saleableTime);
    }

    /**
     * 发放空投藏品
     * @param airdrop
     * @param airdropRecord
     * @return
     */
    @RedisLock(lockKey = LockKey.LOCK_NFT_NUMBER, timeout = 60, waitTime = 30)
    public List<SysUserNft> distributeAirdropNftNumber(@LockObject(valueFields = "sourceId") MarketingAirdrop airdrop, MarketingAirdropRecord airdropRecord,boolean isRandom){
        // 扣除库存
        nftService.takeStock(airdrop.getSourceId(), airdropRecord.getQuantity());
        return _distributeNftNumber(airdrop.getSourceId(), airdropRecord.getUserId(), airdropRecord.getQuantity(), airdrop.getId(), airdropRecord.getId(), NftDispatchSourceEnum.AIRDROP,  UserNftSourceTypeEnum.AIRDROP, isRandom, LocalDateTime.now());
    }


    /**
     * 实际的分发藏品逻辑
     * @param nftId
     * @param userId
     * @param count
     * @param orderMainId
     * @param orderItemId
     * @param dispatchSource
     * @param nftSource
     * @param randomNumber
     * @param saleableTime 可售卖时间
     * @return
     */
    private List<SysUserNft> _distributeNftNumber(Long nftId, Long userId, Integer count,
                                                 Long orderMainId, Long orderItemId,
                                                 NftDispatchSourceEnum dispatchSource, UserNftSourceTypeEnum nftSource,
                                                 boolean randomNumber,
                                                 LocalDateTime saleableTime) {
        Nft nft = nftService.getById(nftId);
        List<NftNumber> nftNumbers = extractUnsoldNftNumber(nft.getId(), count, randomNumber);
        // 如果是装备藏品，则随机装备信息
        if (nft.getNftType() == NftTypeEnum.EQUIPMENT) {
            // 分配时就生成属性
            for (NftNumber nftNumber : nftNumbers) {
                equipmentService.initEquipmentInfo(nft, nftNumber, true);
            }
        }
        // 更新NFT信息
        for (NftNumber nftNumber : nftNumbers) {
            nftNumber.setIsGrant(NftNumberGrantStatusEnum.SOLD);
            nftNumber.setOrderCode(orderMainId);
            nftNumber.setUserId(userId);
            nftNumber.setPayTime(LocalDateTime.now());
        }
        boolean updated = updateBatchById(nftNumbers);
        // 这里分发藏品只可能为首发购买和空头，则不需要冷却时间
        List<SysUserNft> userNfts = userNftService.addUserNft(userId, nftSource, orderMainId, orderItemId, nft, nftNumbers, saleableTime);
        if (!updated) {
            throw new ServiceException("操作失败！");
        }

        // 存储分发记录
        List<NftDispatchRecord> orderNftRecords = new ArrayList<>();
        for (SysUserNft userNft : userNfts) {
            NftDispatchRecord record = new NftDispatchRecord();
            record.setDispatchSource(dispatchSource);
            record.setUserNftId(userNft.getId());
            record.setUserId(userId);
            record.setOrderMainId(orderMainId);
            record.setOrderItemId(orderItemId);
            record.setNftId(userNft.getNftId());
            record.setNftNumberId(userNft.getNftNumberId());
            record.setNftType(nft.getNftType());
            orderNftRecords.add(record);
        }
        boolean saved = nftDispatchRecordService.saveBatch(orderNftRecords);
        if (!saved) {
            throw new ServiceException("操作失败！");
        }

        // 添加上链表
        for (SysUserNft userNft : userNfts) {
            upUpdateChainService.addData(userNft.getId(), nft.getContract(), nft.getId(), userNft.getNftNumberId(), userNft.getNftNumber().getNumberValue(), userNft.getNftType().getValue(), null, userNft.getUserId(), UpChainTypeEnum.MINT);
        }

        return userNfts;
    }


    public UserNFTItemVo selectNftInfo(Long nftNumberId) {
        UserNFTItemVo vo = baseMapper.getNftInfoById(nftNumberId);
        return vo;
    }

    /**
     * 藏品销毁
     */
    @Transactional(rollbackFor = Exception.class)
    public void burnBatch(List<Long> idList){
        if(idList.isEmpty()){
            return ;
        }
        List<NftNumber> nftNumbers = listByIds(idList);
        Long nftId = nftNumbers.get(0).getNftId();
        Nft nft = nftService.getById(nftId);

        for (NftNumber nftNumber : nftNumbers) {
            if(nftNumber.getIsGrant() == NftNumberGrantStatusEnum.BURN){
                throw new ServiceException("重复销毁！");
            }
            if(!Objects.equals(nftNumber.getNftId(), nftId)){
                throw new ServiceException("不能同时销毁多个藏品的编号！");
            }
        }
        // 统计未售出数量，
        int count = (int) nftNumbers.stream().filter(number -> number.getIsGrant() == NftNumberGrantStatusEnum.UNSOLD).count();
        if(count > 0){
            // 获取NFT库存，进行比对
            if(nft.getStock() < count){
                throw new ServiceException("销毁未售出的藏品数量大于藏品库存！");
            }
            nftService.lockStock(nftId, count);
            // 扣除库存
            nftService.takeStock(nftId, count);
        }
        // 开始销毁
        for (NftNumber nftNumber : nftNumbers) {
            burnNftNumber(nftNumber.getId(), nft);
        }
    }

    /**
     * 销毁NFT
     * @param nftNumberId
     */
    @RedisLock(lockKey = LockKey.LOCK_NFT_NUMBER, timeout = 60, waitTime = 30)
    @Transactional(rollbackFor = Exception.class)
    public void burnNftNumber(@LockValue Long nftNumberId, Nft nft){
        NftNumber nftNumber = getById(nftNumberId);
        if(Objects.isNull(nftNumber)){
            throw new ServiceException("典藏不存在！");
        }
        if(nftNumber.getIsGrant() == NftNumberGrantStatusEnum.BURN){
            throw new ServiceException("重复销毁！");
        }
        if(nftNumber.getIsGrant() == NftNumberGrantStatusEnum.SOLD && Objects.isNull(nftNumber.getUserId())){
            throw new ServiceException(StrFormatter.format("用户藏品[{}]未开箱，暂时无法销毁", nftNumber.getNumberValueStr()));
        }
        // 删除用户数据
        if(Objects.nonNull(nftNumber.getUserId())){
            // 获取用户藏品信息
            SysUserNft userNft = userNftService.getUserNftByNftNumberId(nftNumber.getId());
            if(Objects.isNull(userNft)){
                throw new ServiceException(StrFormatter.format("用户藏品[{}]不存在!", nftNumber.getNumberValueStr()));
            }
            if(userNft.getNftStatus() != UserNftStatusEnum.NORMAL){
                throw new ServiceException(StrFormatter.format("用户藏品[{}]不在闲置中，无法销毁！", nftNumber.getNumberValueStr()));
            }
            userNftService.burnUserNft(userNft.getUserId(), Collections.singletonList(userNft.getId()), UserNftStatusEnum.RECYCLE);
            upUpdateChainService.addData(userNft.getId(), nft.getContract(), nft.getId(), nftNumber.getId(), nftNumber.getNumberValue(), nft.getNftType().getValue(), userNft.getUserId(), null, UpChainTypeEnum.BURN);
        }else{
            upUpdateChainService.addData(null, nft.getContract(), nft.getId(), nftNumber.getId(), nftNumber.getNumberValue(), nft.getNftType().getValue(), null, null, UpChainTypeEnum.BURN);
        }
        // 更新状态为销毁
        setNftNumberBurn(nftNumber);
    }


    @RedisLock(lockKey = LockKey.LOCK_NFT_NUMBER, timeout = 60, waitTime = 30)
    public List<NftNumber> distributeBox(Long userId, Long orderId, @LockValue Long nftId, Integer count, Boolean randomNumber) {
        Nft nft = nftService.getById(nftId);
        List<NftNumber> nftNumbers = extractUnsoldNftNumber(nft.getId(), count, randomNumber);
        // 如果是装备藏品，则随机装备信息
        if (nft.getNftType() == NftTypeEnum.EQUIPMENT) {
            // 分配时就生成属性
            for (NftNumber nftNumber : nftNumbers) {
                equipmentService.initEquipmentInfo(nft, nftNumber, true);
            }
        }
        // 更新NFT信息
        for (NftNumber nftNumber : nftNumbers) {
            nftNumber.setIsGrant(NftNumberGrantStatusEnum.SOLD);
            nftNumber.setOrderCode(orderId);
            nftNumber.setPayTime(LocalDateTime.now());
        }

        boolean updated = updateBatchById(nftNumbers);
        if(!updated){
            throw new ServiceException("操作失败！");
        }

        return nftNumbers;
    }


    /**
     * 分发箱子藏品
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public UserNFTItemVo dispatchOpenBoxNftNumber(UserBox userBox, UserBoxNft userBoxNft){
        // 开箱转卖时间跟着箱子走
        SysUserNft userNft = userNftService.addUserNft(userBox.getUserId(), UserNftSourceTypeEnum.OPEN_BOX, userBoxNft.getOrderMainId(), userBoxNft.getOrderItemId(), userBoxNft.getNftType(), userBoxNft.getNftId(), userBoxNft.getNftNumberId(), userBoxNft.getSaleableTime());
        // 存储分发记录
        NftDispatchRecord record = new NftDispatchRecord();
        record.setDispatchSource(NftDispatchSourceEnum.OPEN_BOX);
        record.setUserNftId(userNft.getId());
        record.setUserId(userNft.getUserId());
        record.setOrderMainId(userNft.getOrderMainId());
        record.setOrderItemId(userNft.getOrderItemId());
        record.setNftId(userNft.getNftId());
        record.setNftNumberId(userNft.getNftNumberId());
        record.setNftType(userBoxNft.getNftType());
        boolean saved = nftDispatchRecordService.save(record);
        if (!saved) {
            throw new ServiceException("操作失败！");
        }
        // 添加上链表
        Nft nft = nftService.getById(userNft.getNftId());
        NftNumber nftNumber = getNftNumber(userNft.getNftNumberId());
        changeHoldUser(nftNumber.getId(), userNft.getUserId());
        upUpdateChainService.addData(userNft.getId(), nft.getContract(), userNft.getNftId(), userNft.getNftNumberId(), nftNumber.getNumberValue(), userNft.getNftType().getValue(), null, userNft.getUserId(), UpChainTypeEnum.MINT);
        return userNftService.getNftItemInfo(userNft.getId(), userNft.getUserId());
    }

    /**
     * 获取藏品
     * @param nftNumberId
     * @return
     */
    public NftNumber getNftNumber(Long nftNumberId) {
        NftNumber nftNumber = getById(nftNumberId);
        if(Objects.isNull(nftNumber)){
            throw new ServiceException("典藏信息不存在!");
        }
        return nftNumber;
    }

    public long getNftNumberMaxValue(Long nftId) {
        NftNumber nftNumber = getOne(Wrappers.<NftNumber>lambdaQuery()
                .eq(NftNumber::getNftId, nftId)
                .orderByDesc(NftNumber::getNumberValue)
                .last("LIMIT 1"));
        return nftNumber == null ? 0 : nftNumber.getNumberValue();
    }

    /**
     * 将藏品置为销毁状态
     * @param nftNumber
     */
    @Transactional(rollbackFor = Exception.class)
    public void setNftNumberBurn(NftNumber nftNumber) {
        update(new LambdaUpdateWrapper<NftNumber>()
                .set(NftNumber::getDataEnable, false)
                .set(NftNumber::getIsGrant, NftNumberGrantStatusEnum.BURN)
                .set(NftNumber::getUserId, null)
                .eq(NftNumber::getId, nftNumber.getId()));
    }

    /**
     * 将藏品置为销毁状态 —— 批量销毁
     * @param nftNumberIds
     */
    @Transactional(rollbackFor = Exception.class)
    public void setNftNumberBurns(Long... nftNumberIds) {
        boolean update = update(new LambdaUpdateWrapper<NftNumber>()
                .set(NftNumber::getDataEnable, false)
                .set(NftNumber::getIsGrant, NftNumberGrantStatusEnum.BURN)
                .set(NftNumber::getUserId, null)
                .in(NftNumber::getId, nftNumberIds));
        if(!update){
            throw new ServiceException("有典藏信息不存在！");
        }
    }

    /**
     * 修改藏品持有者用户
     * @param nftNumberId 藏品ID
     * @param userId 持有者iD
     */
    public void changeHoldUser(Long nftNumberId, Long userId) {
        update(Wrappers.<NftNumber>lambdaUpdate()
                .eq(NftNumber::getId, nftNumberId)
                .set(NftNumber::getUserId, userId));
    }

    /**
     * 查询用户持有的藏品
     *
     * @return
     */
    public List<NftNumber> numberListChildren(NftNumberDto params) {
        Long nftId = params.getNftId();
        if (Objects.isNull(nftId)) {
            throw new ServiceException("藏品ID不能为空！");
        }
        Long userId = params.getUserId();
        LambdaQueryWrapper<NftNumber> wp = Wrappers.<NftNumber>lambdaQuery()
                .eq(NftNumber::getNftId, nftId);
        if (Objects.nonNull(userId)) {
            wp.eq(NftNumber::getUserId, userId);
        }
        if (Objects.nonNull(params.getIsGrant())) {
            wp.eq(NftNumber::getIsGrant, params.getIsGrant());
            if (params.getIsGrant() == NftNumberGrantStatusEnum.SOLD) {
                wp.isNotNull(NftNumber::getUserId);
            }
        }
        if (StrUtil.isNotBlank(params.getKeyword())) {
            wp.eq(NftNumber::getNumberValueStr, params.getKeyword());
        }
        return list(wp);
    }

    /**
     * 根据藏品numberValueStr编码集合获取numberId信息
     */
    public List<Long> getNftNumberIdsByNumberValueStr(List<String> numberStrs) {
        List<NftNumber> list = list(Wrappers.<NftNumber>lambdaQuery()
                .select(NftNumber::getId)
                .in(NftNumber::getNumberValueStr, numberStrs));
        return list.stream().map(NftNumber::getId).collect(Collectors.toList());
    }
}
